#include "stdafx.h"
#include "CppUnitTest.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

#include <vex/functional/any_delegate.h>
#include <vex/functional/make_any_delegate.h>
#include <vex.tests/mstest/tests.core/tests.core.handle_policy.h>

namespace tests { namespace tests_functional_any_delegate {		

    namespace vf = vex::functional;
    namespace mpl = boost::mpl;
    namespace bft = boost::function_types;

    TEST_CLASS(test_class)
    {
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.functional.any_delegate")
        END_TEST_CLASS_ATTRIBUTE();
    public:
        VEX_TESTS_TRACKED_MEM_CHECK();

        TEST_METHOD_INITIALIZE(set_up)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_START();
        }

        TEST_METHOD_CLEANUP(tear_down)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_FINISH();
        }

        template<class TSignature>
        void any_delegate_type_can_be_constructed_from_supported_signatures_impl()
        {
            typedef bft::components<TSignature> func_comp_t;
            typedef vf::any_delegate<TSignature> delegate_t;
            typedef vf::contract::delegate<TSignature> exp_delegate_contract_t;

            // delegate contract
            Assert::IsTrue(
                typeid(exp_delegate_contract_t) ==
                typeid(typename delegate_t::contract_type), wlog_message()
            << "expected: " << typeid(exp_delegate_contract_t).name() << std::endl
            << "got: " << typeid(typename delegate_t::contract_type).name() << std::endl 
            | as_string);

            Assert::IsTrue(std::is_same<
                exp_delegate_contract_t, 
                typename delegate_t::contract_type
            >::value, wlog_message()
            << "expected: " << typeid(exp_delegate_contract_t).name() << std::endl
            << "got: " << typeid(typename delegate_t::contract_type).name() << std::endl 
            | as_string);

            // result type
            Assert::IsTrue(std::is_same<
                typename mpl::deref< typename mpl::begin<func_comp_t>::type >::type, 
                typename delegate_t::result_type
            >::value, wlog_message()
            << "expected: " << typeid(typename mpl::deref< typename mpl::begin<func_comp_t>::type >::type).name() << std::endl
            << "got: " << typeid(typename delegate_t::result_type).name() << std::endl
            | as_string);
        }

        TEST_METHOD(any_delegate_type_can_be_constructed_from_nullary_signature)
        {
            this->any_delegate_type_can_be_constructed_from_supported_signatures_impl<
                void ()>();
            vf::any_delegate<void ()> d;
            Assert::IsTrue(std::is_same<void, decltype(d())>::value);
        }

        TEST_METHOD(any_delegate_type_can_be_constructed_from_supported_signatures)
        {
            this->any_delegate_type_can_be_constructed_from_supported_signatures_impl<
                void ()>();
            this->any_delegate_type_can_be_constructed_from_supported_signatures_impl<
                int ()>();
            this->any_delegate_type_can_be_constructed_from_supported_signatures_impl<
                vf::contract::delegate<int()>* ()>();

            this->any_delegate_type_can_be_constructed_from_supported_signatures_impl<
                void (int, double)>();
            this->any_delegate_type_can_be_constructed_from_supported_signatures_impl<
                void (int, double, std::shared_ptr<double> const &)>();
            this->any_delegate_type_can_be_constructed_from_supported_signatures_impl<
                void (int, double, std::shared_ptr<double> const &, std::string &)>();
            this->any_delegate_type_can_be_constructed_from_supported_signatures_impl<
                void (int, double, std::shared_ptr<double> const &, std::string &, std::vector<int>&&)>();
        }

        TEST_METHOD(any_delegate_call_operator_can_be_invoked_with_proper_arguments)
        {
            {
                vf::any_delegate<void ()> d;
                Assert::IsTrue(std::is_same<void, decltype(d())>::value);
            }

            {
                vf::any_delegate<int (std::string const&)> d;
                Assert::IsTrue(std::is_same<int, decltype(d(std::declval<std::string const &>()))>::value);
            }

            {
                vf::any_delegate<int (std::string const&, int, double, std::shared_ptr<int> const &)> d;
                Assert::IsTrue(std::is_same<int, decltype(d(
                    std::declval<std::string const &>(),
                    std::declval<int>(),
                    std::declval<double>(),
                    std::declval<std::shared_ptr<int> const &>()
                    ))>::value);
            }
        }

        template<class TSignature, class TFunc>
        auto make_delegate_contract(TFunc&&)
            -> vf::contract::delegate<TSignature>*
        {
            return nullptr;
        }

        template<class TSignature, class TFunc>
        auto make_any_delegate(TFunc&& p_f)
            -> vf::any_delegate<TSignature>
        {
            return vex::as_any_from_output() | 
                this->make_delegate_contract<TSignature>(std::forward<TFunc>(p_f));
        }

        static void test_foo() {}
        void test_foo_member() {}

        TEST_METHOD(any_delegate_instance_can_be_constructed_from_raw_contract)
        {
            {
                typedef decltype(this->make_any_delegate<void ()>(test_foo)) 
                    delegate_t;

                auto d = this->make_any_delegate<void()>(test_foo);
                typedef decltype (d) constr_delegate_t;
            }

            {
                typedef decltype(this->make_any_delegate<void ()>(
                    std::bind(&test_class::test_foo_member, this))) 
                    delegate_t;
            }

            {
                auto foo = [=]() { this->test_foo_member(); }; 
                typedef decltype(this->make_any_delegate<void ()>(
                    foo)) delegate_t;
            }
        }

        TEST_METHOD(any_delegate_supports_move)
        {
            auto t_d1 = vf::make_any_delegate<void ()>([]() {});
            auto t_d2 = std::move(t_d1);
            
            Assert::IsFalse(t_d1);
            Assert::IsNull(t_d1.get());
            Assert::IsTrue(t_d2);
            Assert::IsNotNull(t_d2.get());
        }

        TEST_METHOD(any_delegate_supports_swap)
        {
            auto t_d1 = vf::make_any_delegate<int ()>([]() -> int { return 1; });
            auto t_d2 = vf::make_any_delegate<int ()>([]() -> int { return 2; });

            Assert::IsTrue(t_d1);
            Assert::IsTrue(t_d2);
            Assert::AreEqual(1, t_d1());
            Assert::AreEqual(2, t_d2());

            t_d1.swap(t_d2);

            Assert::AreEqual(2, t_d1());
            Assert::AreEqual(1, t_d2());
        }

        TEST_METHOD(any_delegate_implements_handle_policy)
        {
            {
                vf::any_delegate<void ()> l, r;
                handle_policy::correctly_implements_handle_policy(l, r);
            }

            {
                vf::any_delegate<void (int, double)> l, r;
                handle_policy::correctly_implements_handle_policy(l, r);
            }
        }
    };

}}